﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;


namespace DTO
{
    using s32 = System.Int32;
    using u8 = System.Byte;
    using u32 = System.UInt32;

    public class CReadFile
    {
        #region attibute
        Stream m_pFile;

#if WINDOWS_PHONE
        private IsolatedStorageFile             m_storage;
#endif

#if CACHE_RES_FILE

#else
        s32 m_nPos;				//Cache pos
        private const s32 m_nCacheSize = 64 * 1024;
        private static u8[] m_pCache = new u8[m_nCacheSize];
        s32 m_nAvailable;
#endif
        s32 m_ResourceLength;
        #endregion
        #region method
        public CReadFile OpenResource(string in_pResource, int in_cache, int in_nOffset)
        {
            try
            {
                m_pFile = File.Open(in_pResource, FileMode.Open);
            }
            catch (Exception)
            {
                return null;
            }

            if (SetStream(in_nOffset) == false)
                return null;

            //if (m_pCache == null)
            //{
            //    Release();
            //    return null;
            //}

            return this;
        }

        public void SetStream(Stream s, int in_nOffset)
        {
            m_pFile = s;
            SetStream(in_nOffset);
        }

        private bool SetStream(int in_nOffset)
        {
            m_ResourceLength = (s32)m_pFile.Length;

            if (in_nOffset < 0)
                in_nOffset = 0;
            else if (in_nOffset >= m_ResourceLength)
            {
                Release();
                return false;
            }
            m_pFile.Seek(in_nOffset, SeekOrigin.Begin);

            //m_nCacheSize = 64 * 1024;	//16 cache size
            //m_pCache = new u8[m_nCacheSize];
            m_nPos = 0;
            m_nAvailable = 0;

            return true;
        }

        public virtual bool OpenUserFile(string fileName, int startOffset)
        {
            if (string.IsNullOrEmpty(fileName) == true)
                return false;

#if WINDOWS_PHONE
            if (m_storage == null)
                m_storage = IsolatedStorageFile.GetUserStoreForApplication();
#endif

            try
            {
                if (UserFileExists(fileName) == true)
                {
#if WINDOWS_PHONE
                    m_pFile = new IsolatedStorageFileStream(fileName, System.IO.FileMode.Open, m_storage);
#else
                    m_pFile = File.Open(fileName, FileMode.Open);
#endif
                    SetStream(startOffset);
                }
                else
                    return false;
            }
            catch (Exception e)
            {
                return false;
            }

            return true;
        }

        public void Release()
        {
#if CACHE_RES_FILE
		m_bufferIdx = -1;
		m_tellPos = 0;
		m_ResourceLength = 0;
#else
            if (m_pFile != null)
            {
                m_pFile.Close();
                m_pFile = null;
            }

#endif
#if WINDOWS_PHONE
            if (m_storage != null)
            {
                m_storage.Dispose();
                m_storage = null;
            }
#endif
        }

        public float ReadFloat()
        {
            Read(fourub, 4);
            return BitConverter.ToSingle(fourub, 0);
        }

        public bool ReadFloat(out float out_pData)
        {
            Read(fourub, 4);
            out_pData = BitConverter.ToSingle(fourub, 0);
            return true;
        }

        public float ReadF32()
        {
            Read(fourub, 4);
            return BitConverter.ToSingle(fourub, 0);
        }

        public int ReadInt()
        {
            Read(fourub, 4);
            return BitConverter.ToInt32(fourub, 0);
        }

        public bool ReadInt(out int out_pData)
        {
            Read(fourub, 4);
            out_pData = ((fourub[0] & 0xFF)) +
                   ((fourub[1] & 0xFF) << 8) +
                   ((fourub[2] & 0xFF) << 16) +
                   ((fourub[3] & 0xFF) << 24);
            return true;
        }

        public uint ReadU32()
        {
            Read(fourub, 4);
            return BitConverter.ToUInt32(fourub, 0);

        }

        private static u8[] fourub = new u8[4];
        public int ReadS32()
        {
            Read(fourub, 4);
            return BitConverter.ToInt32(fourub, 0);

        }

        public short ReadS16()
        {
            Read(twoub, 2);
            return (short)(twoub[0] + (twoub[1] << 8));

        }
        public ushort ReadU16()
        {
            Read(twoub, 2);
            return (ushort)(twoub[0] + (twoub[1] << 8));

        }

        public byte ReadU8()
        {
            byte re = 0;
            ReadByte(out re);
            return re;
        }

        public bool ReadBool()
        {
            byte re = 0;
            ReadByte(out re);
            return (re != 0);
        }

        internal sbyte ReadS8()
        {
            sbyte re = 0;
            ReadByte(out re);
            return re;
        }

        public bool ReadShort(out short out_pData)
        {
            Read(twoub, 2);
            out_pData = (short)(twoub[0] + (twoub[1] << 8));
            return true;
        }

        private static u8[] twoub = new u8[2];
        public bool ReadShortBE(out short out_pData)
        {
            Read(twoub, 2);
            out_pData = (short)(twoub[1] + (twoub[0] << 8));
            return true;
        }

        private static u8[] oneub = new u8[1];
        public bool ReadByte(out byte out_pData)
        {
            Read(oneub, 1);
            out_pData = oneub[0];
            return true;
        }

        private static sbyte[] oneb = new sbyte[1];
        public bool ReadByte(out sbyte out_pData)
        {
            Read(oneb, 1);
            out_pData = oneb[0];
            return true;
        }

        public int Read(ushort[] out_pData, int in_nSize)
        {
            ushort[] pOut = out_pData;
            int initial = in_nSize;

#if CACHE_RES_FILE
	if( ( m_tellPos + in_nSize ) > m_ResourceLength )
	{
		DEBUG_OUT("!!!_____TRY TO READ TO MUCH_____!!!\n");
		return -1;
	}
	GX_MEMCPY(pOut, m_pFileBuffers[m_bufferIdx] + m_tellPos, in_nSize);
	m_tellPos +=in_nSize;
#else
            int index = 0;
            do
            {
                int nSize = (int)Math.Min(in_nSize, m_nAvailable);

                //Copy from cache
                if (nSize > 0)
                {
                    System.Buffer.BlockCopy(m_pCache, m_nPos, pOut, index, nSize);
                    index += nSize;
                    m_nAvailable -= nSize;
                    m_nPos += nSize;
                    in_nSize -= nSize;
                }

                if (in_nSize != 0 && m_nAvailable <= 0)
                {
                    FillCache( Math.Min( in_nSize, m_nCacheSize ) );
                }
            } while (in_nSize > 0 && m_nAvailable > 0);
#endif
            return in_nSize;
        }

        public int Read(short[] out_pData, int in_nSize)
        {
            short[] pOut = out_pData;
            int initial = in_nSize;

#if CACHE_RES_FILE
	if( ( m_tellPos + in_nSize ) > m_ResourceLength )
	{
		DEBUG_OUT("!!!_____TRY TO READ TO MUCH_____!!!\n");
		return -1;
	}
	GX_MEMCPY(pOut, m_pFileBuffers[m_bufferIdx] + m_tellPos, in_nSize);
	m_tellPos +=in_nSize;
#else
            int index = 0;
            do
            {
                int nSize = (int)Math.Min(in_nSize, m_nAvailable);

                //Copy from cache
                if (nSize > 0)
                {
                    System.Buffer.BlockCopy(m_pCache, m_nPos, pOut, index, nSize);
                    index += nSize;
                    m_nAvailable -= nSize;
                    m_nPos += nSize;
                    in_nSize -= nSize;
                }

                if (in_nSize != 0 && m_nAvailable <= 0)
                {
                    FillCache(Math.Min(in_nSize, m_nCacheSize));
                }
            } while (in_nSize > 0 && m_nAvailable > 0);
#endif
            return in_nSize;
        }

        public int Read(uint[] out_pData, int in_nSize)
        {
            uint[] pOut = out_pData;
            int initial = in_nSize;

#if CACHE_RES_FILE
	if( ( m_tellPos + in_nSize ) > m_ResourceLength )
	{
		DEBUG_OUT("!!!_____TRY TO READ TO MUCH_____!!!\n");
		return -1;
	}
	GX_MEMCPY(pOut, m_pFileBuffers[m_bufferIdx] + m_tellPos, in_nSize);
	m_tellPos +=in_nSize;
#else
            int index = 0;
            do
            {
                int nSize = (int)Math.Min(in_nSize, m_nAvailable);

                //Copy from cache
                if (nSize > 0)
                {
                    System.Buffer.BlockCopy(m_pCache, m_nPos, pOut, index, nSize);
                    index += nSize;
                    m_nAvailable -= nSize;
                    m_nPos += nSize;
                    in_nSize -= nSize;
                }

                if (in_nSize != 0 && m_nAvailable <= 0)
                {
                    FillCache(Math.Min(in_nSize, m_nCacheSize));
                }
            } while (in_nSize > 0 && m_nAvailable > 0);
#endif
            return in_nSize;
        }

        public int Read(int[] out_pData, int in_nSize)
        {
            int[] pOut = out_pData;
            int initial = in_nSize;

#if CACHE_RES_FILE
	if( ( m_tellPos + in_nSize ) > m_ResourceLength )
	{
		DEBUG_OUT("!!!_____TRY TO READ TO MUCH_____!!!\n");
		return -1;
	}
	GX_MEMCPY(pOut, m_pFileBuffers[m_bufferIdx] + m_tellPos, in_nSize);
	m_tellPos +=in_nSize;
#else
            int index = 0;
            do
            {
                int nSize = (int)Math.Min(in_nSize, m_nAvailable);

                //Copy from cache
                if (nSize > 0)
                {
                    System.Buffer.BlockCopy(m_pCache, m_nPos, pOut, index, nSize);
                    index += nSize;
                    m_nAvailable -= nSize;
                    m_nPos += nSize;
                    in_nSize -= nSize;
                }

                if (in_nSize != 0 && m_nAvailable <= 0)
                {
                    FillCache(Math.Min(in_nSize, m_nCacheSize));
                }
            } while (in_nSize > 0 && m_nAvailable > 0);
#endif
            return in_nSize;
        }

        public int Read(float[] out_pData, int in_nSize)
        {
            float[] pOut = out_pData;
            int initial = in_nSize;

#if CACHE_RES_FILE
	if( ( m_tellPos + in_nSize ) > m_ResourceLength )
	{
		DEBUG_OUT("!!!_____TRY TO READ TO MUCH_____!!!\n");
		return -1;
	}
	GX_MEMCPY(pOut, m_pFileBuffers[m_bufferIdx] + m_tellPos, in_nSize);
	m_tellPos +=in_nSize;
#else
            int index = 0;
            do
            {
                int nSize = (int)Math.Min(in_nSize, m_nAvailable);

                //Copy from cache
                if (nSize > 0)
                {
                    System.Buffer.BlockCopy(m_pCache, m_nPos, pOut, index, nSize);
                    index += nSize;
                    m_nAvailable -= nSize;
                    m_nPos += nSize;
                    in_nSize -= nSize;
                }

                if (in_nSize != 0 && m_nAvailable <= 0)
                {
                    FillCache(Math.Min(in_nSize, m_nCacheSize));
                }
            } while (in_nSize > 0 && m_nAvailable > 0);
#endif
            return in_nSize;
        }       

        public int Read(u8[] out_pData, int in_nSize)
        {
            return Read(out_pData, in_nSize, 0);
        }
        public int Read(u8[] out_pData, int in_nSize, int offset)
        {
            u8[] pOut = out_pData;
            int initial = in_nSize;

#if CACHE_RES_FILE
	if( ( m_tellPos + in_nSize ) > m_ResourceLength )
	{
		DEBUG_OUT("!!!_____TRY TO READ TO MUCH_____!!!\n");
		return -1;
	}
	GX_MEMCPY(pOut, m_pFileBuffers[m_bufferIdx] + m_tellPos, in_nSize);
	m_tellPos +=in_nSize;
#else
            int index = 0;
            do
            {
                int nSize = (int)Math.Min(in_nSize, m_nAvailable);

                //Copy from cache
                if (nSize > 0)
                {
                    System.Buffer.BlockCopy(m_pCache, m_nPos, pOut, index + offset, nSize);
                    index += nSize;
                    m_nAvailable -= nSize;
                    m_nPos += nSize;
                    in_nSize -= nSize;
                }

                if (in_nSize != 0 && m_nAvailable <= 0)
                {
                    FillCache(Math.Min(in_nSize, m_nCacheSize));
                }
            } while (in_nSize > 0 && m_nAvailable > 0);
#endif
            return (initial - in_nSize);
        }

        public int Read(sbyte[] out_pData, int in_nSize)
        {
            sbyte[] pOut = out_pData;
            int initial = in_nSize;

#if CACHE_RES_FILE
	if( ( m_tellPos + in_nSize ) > m_ResourceLength )
	{
		DEBUG_OUT("!!!_____TRY TO READ TO MUCH_____!!!\n");
		return -1;
	}
	GX_MEMCPY(pOut, m_pFileBuffers[m_bufferIdx] + m_tellPos, in_nSize);
	m_tellPos +=in_nSize;
#else
            int index = 0;
            do
            {
                int nSize = (int)Math.Min(in_nSize, m_nAvailable);

                //Copy from cache
                if (nSize > 0)
                {
                    System.Buffer.BlockCopy(m_pCache, m_nPos, pOut, index, nSize);
                    index += nSize;
                    m_nAvailable -= nSize;
                    m_nPos += nSize;
                    in_nSize -= nSize;
                }

                if (in_nSize != 0 && m_nAvailable <= 0)
                {
                    FillCache(Math.Min(in_nSize, m_nCacheSize));
                }
            } while (in_nSize > 0 && m_nAvailable > 0);
#endif
            return (initial - in_nSize);
        }
        public s32 GetLength()
        {
            return m_ResourceLength;
        }

        public long FTell()
        {
            return m_pFile.Position - m_nAvailable;
        }

        public void FillCache( int cacheSize )
        {

            s32 sizeRemaining = (s32)(GetLength() - FTell());
            s32 nSize = Math.Min(cacheSize, sizeRemaining);

            m_nPos = 0;

            m_nAvailable = m_pFile.Read(m_pCache, 0, nSize);
        }

        public static CReadFile Open(string in_pResource)
        {
            return Open(in_pResource, 0, 0, false);
        }

        public static CReadFile Open(string in_pResource, bool isUserFile)
        {
            return Open(in_pResource, 0, 0, isUserFile);
        }

        public static CReadFile Open(string in_pResource, int in_cache, int in_nOffset, bool isUserFile)
        {
            CReadFile pFile = new CReadFile();
            if (pFile == null)
                return null;

            if (isUserFile == false)
            {
                if (pFile.OpenResource(in_pResource, in_cache, in_nOffset) == null)
                    return null;
            }
            else
            {
                if (pFile.OpenUserFile(in_pResource, in_nOffset) == false)
                    return null;
            }
            //if (pFile.OpenResource(in_pResource, in_cache, in_nOffset) == null)
            //    SAFE_DEL(pFile);

            return pFile;
        }

        public int Skip(s32 value)
        {

            //DEBUG_OUT("CReadFile::Skip(s32 %d)\n", value);
            if (value <= 0)
                return 0;


            //We have cached data
            if (m_nAvailable > 0)
            {
                int nSkip = Math.Min(value, m_nAvailable);

                //Soft skip
                m_nAvailable -= nSkip;
                m_nPos += nSkip;
                value -= nSkip;
            }


            //See if we still have to skip
            if (value > 0)
            {
                //Hard skip
                m_pFile.Seek(value, SeekOrigin.Current);

            }

            return 0;

        }

        public void FSeek(uint offset)
        {
            FSeek(offset, 0);
        }

        public void FSeek(uint offset, uint start_offset)
        {
            uint ammount = start_offset + offset;
            if (m_pFile != null)
            {
                m_nAvailable = 0;
                m_nPos = 0;
                m_pFile.Seek(ammount, SeekOrigin.Begin);
            }
        }

        /// <summary>
        /// Checks if a certain user file exists.
        /// </summary>
        /// <param name="fileName">name of the user file to check</param>
        /// <returns>true if the file exists; false, otherwise</returns>
        public static bool UserFileExists(string fileName)
        {
            try
            {
#if WINDOWS_PHONE
                return IsolatedStorageFile.GetUserStoreForApplication().FileExists(fileName);
#else
                return File.Exists(fileName);
#endif
            }
            catch
            {
            }

            return false;
        }

        #endregion
    
        internal char ReadChar()
        {
            u8[] i = new u8[1];
            Read(i, 1);
            return (char)i[0];
        }

    
        private static u8[] ii = new u8[8];
        public int ReadS64()
        {            
            Read(ii, 8);
            return BitConverter.ToInt32(ii, 0);

        }

        internal double ReadF64()
        {            
            Read(ii, 8);
            return BitConverter.ToDouble(ii, 0);
        }
    }
}
